/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Paint;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.RoundRectangle2D;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JMenu;
import javax.swing.plaf.basic.BasicMenuUI;

/**
 * @version 1.0 
 * @author Dimitrij Pankratz, Anton Musichi
 */
public class PearMenuUI extends BasicMenuUI {
	
	//colors and strokes
	
	/**
	 * The color of the border of the menu.
	 */
	private Color borderColor = new Color(0x66, 0x66, 0x66);
	
	/**
	 * Thickness of the border.
	 */
	private Stroke borderStroke = new BasicStroke(1.0f);

	/**
	 * The highlight color of the border.
	 */
	private Color borderHighlightColor = new Color(255, 255, 255, 240);
	
	/**
	 * Thickness of the border highlighting.
	 */
	private Stroke borderHighlightStroke = new BasicStroke(1.0f);
	
	/**
	 * The start color of the gradient of menu, while mouse hover.
	 */
	protected Color hoverGradientTop = new Color(0xe7, 0xe7, 0xe7);
	
	/**
	 * The end color of the gradient of menu, while mouse hover.
	 */
	protected Color hoverGradientBottom = new Color(0xca, 0xca, 0xca);
	
	/**
	 * The start color of the gradient of menu, while mouse is pressed.
	 */
	protected Color pressedGradientTop = new Color(0xb7, 0xb7, 0xb7);
	
	/**
	 * The end color of the gradient of menu, while mouse is pressed.
	 */
	protected Color pressedGradientBottom = new Color(0x90, 0x90, 0x90);
	
	
	//insets
	
	/**
	 * Left padding of the menu.
	 */
	protected int leftInset = 6;
	
	/**
	 * Right padding of the menu.
	 */
	protected int rightInset = 6;
	
	/**
	 * Top padding of the menu.
	 */
	protected int topInset = 3;
	
	/**
	 * Bottom padding of the menu.
	 */
	protected int bottomInset = 3;
	
	
	//margins
	
	/**
	 * Left margin of the menu.
	 */
	protected int leftMargin = 2;
	
	/**
	 * Right margin of the menu.
	 */
	protected int rightMargin = 2;
	
	/**
	 * Top margin of the menu.
	 */
	protected int topMargin = 5;
	
	/**
	 * Bottom margin of the menu.
	 */
	protected int bottomMargin = 6;
	
	//paint states
	//private final static int FIRST_PAINT = 0;
	
	/**
	 * State of the menu while mouse is not over or pressed.
	 */
	private final static int NORMAL_PAINT = 1;
	
	/**
	 * State of the menu while mouse is over, but not pressed.
	 */
	private final static int HOVER_PAINT = 2;
	
	/**
	 * State of the menu while mouse is pressed.
	 */
	private final static int SELECTED_PAINT = 3;
	
	/**
	 * Current last state of the button.
	 */
	protected int lastState = 0;
	
	/**
	 * If true, the optimizer is used, otherwise optimizer is off
	 */
	protected boolean drawOptimizerOn = true;
	
	@Override
	public void paint(Graphics g, JComponent c) {
		JMenu menuButton = (JMenu) c;
		ButtonModel menuButtonModel = menuButton.getModel();

		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
				RenderingHints.VALUE_ANTIALIAS_ON);
		
		//text metrics
		String text = menuButton.getText();
		FontMetrics textMetrics = g.getFontMetrics(g.getFont());
		int textWidth = textMetrics.stringWidth(text);
		int textHeight = textMetrics.getHeight();
		
		//calculate the size of the menu point
		int menuWidth = this.calculateWidth(c);
		int menuHeight = this.calculateHeight(c);
		
		//calculate the size of the button
		int buttonWidth = textWidth + leftInset + rightInset;
		int buttonHeight = textHeight + topInset + bottomInset;
		
		//adjust the size of the menu point
		c.setSize(menuWidth, menuHeight);
		c.setSize(new Dimension(menuWidth, menuHeight));
		c.setPreferredSize(new Dimension(menuWidth, menuHeight));
		c.setMinimumSize(new Dimension(menuWidth, menuHeight));
		c.setBounds(c.getX(), c.getY(), menuWidth, menuHeight);

		//calculate the position of the button
		int x = leftMargin;
		int y = topMargin;
		double arc = (double) buttonHeight * .3;

		boolean isPressed = menuButtonModel.isSelected();
		
		boolean mouseOver = isMouseOver(c) || menuButtonModel.isRollover();
		
		if (!isPressed && !mouseOver) {
			if (this.lastState != PearMenuUI.NORMAL_PAINT  && drawOptimizerOn) {
				paintMenuBlank(g2d, 
						menuButton, 
						c.getHeight(), 
						c.getWidth());
				this.lastState = PearMenuUI.NORMAL_PAINT;
			}
		}
		
		if (!isPressed && mouseOver || isPressed && !mouseOver) {
			if (this.lastState != PearMenuUI.HOVER_PAINT && drawOptimizerOn) {
				paintMenuBackGround(g2d, 
						menuButton, 
						x, 
						y, 
						buttonWidth, 
						buttonHeight, 
						arc, 
						isPressed);
				paintMenuBorder(g2d, 
						menuButton, 
						x, 
						y, 
						buttonWidth,
						buttonHeight, 
						arc);
				this.lastState = PearMenuUI.HOVER_PAINT;
			}
		}
		
		if (isPressed && mouseOver) {
			if (this.lastState != PearMenuUI.SELECTED_PAINT && drawOptimizerOn){
				paintMenuBackGround(g2d, 
						menuButton, 
						x, 
						y, 
						buttonWidth, 
						buttonHeight, 
						arc, 
						isPressed);
				paintMenuBorder(g2d, 
						menuButton, 
						x, 
						y, 
						buttonWidth, 
						buttonHeight, 
						arc);
				this.lastState = PearMenuUI.SELECTED_PAINT;
			}
		}
		
		// Draw the Text
        Rectangle textRect = new Rectangle();
        textRect.x = leftMargin + leftInset;
        textRect.y = topMargin + topInset;
        if(text != null) {
        	paintText(g, menuButton, textRect, text);
		}
	}
	
	/**
	 * Paints a button with colored background and no other features.
	 * 
	 * @param g Graphics object to paint on.
	 * @param b The button of the menu.
	 * @param w The width of the menu button.
	 * @param h The height of the menu button.
	 */
	protected void paintMenuBlank(Graphics2D g, AbstractButton b, 
			int h, int w) {
		
		Graphics gcopy = g.create();
		gcopy.setColor(new Color(0xd6, 0xd6, 0xd6));
		gcopy.fillRect(0, 0, w, h);
		gcopy.dispose();
	}
	@Override
	protected void paintText(Graphics g, JMenuItem menuItem, 
			Rectangle textRect, String text) {
		
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
				RenderingHints.VALUE_ANTIALIAS_ON);
		
		g2d.setStroke(new BasicStroke(2.0f));
		ButtonModel model = menuItem.getModel();
		FontMetrics fm = g2d.getFontMetrics();
		
		//draw the shadow with color depending on selected button
		g2d.setColor(new Color(0xededed));
		if (model.isArmed() || 
				(menuItem instanceof JMenu && model.isSelected())) {  	
		    g2d.setColor(new Color(0xbdbdbd));
		}
		g2d.drawString(text, textRect.x, textRect.y+1 + fm.getAscent());
		
		//draw the text
		g2d.setColor(Color.BLACK);
		g2d.drawString(text, textRect.x, textRect.y + fm.getAscent());
	}

	/**
	 * Paints the background of the menu button 
	 * depending on the mouse behavior.
	 * 
	 * @param g Graphics object to paint on.
	 * @param b The button of the menu.
	 * @param x The position of the button on the x axis.
	 * @param y The position of the button on the y axis.
	 * @param w The width of the menu button.
	 * @param h The height of the menu button.
	 * @param arc The roundness of the menu button.
	 * @param isPressed True if the mouse is pressed on menu, otherwise false.
	 */
	protected void paintMenuBackGround(Graphics2D g, AbstractButton b, 
			int x, int y, int w, int h, double arc, boolean isPressed) {
		
		Shape menuShape = new RoundRectangle2D.Double((double) x, 
				(double) y, (double) w, (double) h, arc, arc);
		Shape clip = g.getClip();
		int arc4 = (int) (arc / 4);
		
		g.setClip(menuShape);
		g.setColor(Color.WHITE);
		g.fillRect(x, y, w, h);

		//mouse over background
		if (!isPressed)
		{
			Paint vPaint = new GradientPaint(0, 
					y, 
					this.hoverGradientTop, 
					0, 
					y + h, 
					this.hoverGradientBottom);
			g.setPaint(vPaint);
			g.translate(0, arc4);
			g.fillRoundRect(x, y, w, h  - 1, (int) arc, (int) arc);
		}
		//mouse pressed background
		else
		{
			Paint vPaint = new GradientPaint(0, 
					y, 
					this.pressedGradientTop, 
					0, 
					y + h, 
					this.pressedGradientBottom);
			g.setPaint(vPaint);
			g.translate(0, arc4);
			g.fillRoundRect(x, y, w, h, (int) arc, (int) 0);
		}

		g.translate(0, -1 * arc4);
		g.setClip(clip);
	}

	/**
	 * Paints the border of the menu button.
	 * 
	 * @param g Graphics object to paint on.
	 * @param b The button of the menu.
	 * @param x The position of the button on the x axis.
	 * @param y The position of the button on the y axis.
	 * @param w The width of the menu button.
	 * @param h The height of the menu button.
	 * @param arc The roundness of the menu button.
	 */
	private void paintMenuBorder(Graphics2D g, AbstractButton c, 
			int x, int y, int w, int h, double arc) {
		
		Shape menuShape = new RoundRectangle2D.Double((double) x, 
				(double) y, (double) w, (double) h, arc, arc);
		Shape clip = g.getClip();
		
		g.setClip(x,y+h-(h/2),w,y+h);
		g.setColor(borderHighlightColor);
		g.setStroke(borderHighlightStroke);
		g.translate(0, 1);
		g.draw(menuShape);

		g.setClip(clip);
		g.setColor(borderColor);
		g.setStroke(borderStroke);
		g.translate(0, -1);
		g.draw(menuShape);
	}
	
	/**
	 * Calculates the preferred height for the given component.
	 * 
	 * @param c The component to calculate the height for.
	 * @return The calculated preferred height for the component.
	 */
	public int calculateHeight(JComponent c) {
		JMenu menuButton = (JMenu) c;
		
		//text metrics
		FontMetrics textMetrics = 
			menuButton.getFontMetrics(menuButton.getFont());
		int textHeight = textMetrics.getHeight();
		
		//calculate the size of the menu point
		int menuHeight = 
			textHeight + topMargin + bottomMargin + topInset + bottomInset;
		
		return menuHeight;
	}
	
	/**
	 * Calculates the preferred width for the given component.
	 * 
	 * @param c The component to calculate the width for.
	 * @return The calculated preferred width for the component.
	 */
	public int calculateWidth(JComponent c) {
		JMenu menuButton = (JMenu) c;
		
		//text metrics
		String text = menuButton.getText();
		FontMetrics textMetrics = 
			menuButton.getFontMetrics(menuButton.getFont());
		int textWidth = textMetrics.stringWidth(text);
		
		//calculate the size of the menu point
		int menuWidth = 
			textWidth + leftMargin + rightMargin + leftInset + rightInset;
		
		return menuWidth;
	}
	
	@Override
	public Dimension getPreferredSize(JComponent c) {
		if(c instanceof JMenu)
			return new Dimension(calculateWidth(c), calculateHeight(c));
		else
			return super.getPreferredSize(c);
	}
	
	@Override
	public Dimension getMinimumSize(JComponent c) {
		if(c instanceof JMenu)
			return getPreferredSize(c);
		else
			return super.getMinimumSize(c);
	}
	
	@Override
	public Dimension getMaximumSize(JComponent c) {
		if(c instanceof JMenu)
			return getPreferredSize(c);
		else
			return super.getMaximumSize(c);
	}
	
	private boolean isMouseOver(JComponent c) {
		PointerInfo info = MouseInfo.getPointerInfo();
        Point locMouse = info.getLocation(); 
        Point locComp = c.getLocationOnScreen();
        
        Rectangle rect = new Rectangle((int)locComp.getX(), (int)locComp.getY(), 
        		c.getWidth(), c.getHeight());
        
        return rect.contains(locMouse);
	}
}